#include <util/message.hpp>
#include "vertex.hpp"

namespace {
  util::message_stream<char> vverr ("\nVV ERROR: ");
  util::message_stream<char> vvwarn("\nVV WARNING: ");
}

/** @brief Null abstract_vertex constructor
 */
vv::vptr::vptr() :
  ptr(0)
{}

/** @brief Constructor from a raw abstract_vertex pointer

    This constructor should usually only be used inside the library
    calls.
*/
vv::vptr::vptr(abstract_vertex* v) :
  ptr(0)
{
  release();
  if (v) {
    ptr = v;
    v->refp();
  }
}

/** @brief Constructor from another vptr.

    This is usual constructor that should be used for handling
    vertices outside the library.
*/
vv::vptr::vptr(const vptr& v) :
  ptr(0)
{
  release();
  if (v) {
    ptr = v.raw();
    v->refp();
  }
}

/** @brief Destructor */
vv::vptr::~vptr() {
  release();
}

/** @brief Assignment operator */
vv::vptr& vv::vptr::operator=(const vv::vptr& v) {
  release();
  if (v) {
    ptr = v.raw();
    v->refp();
  }
  return *this;
}

/** @brief Member indirection operator

    The use of the release_old() prior to the inderection call is a
    prefix operation (Wrapping C++ Member Function Calls,
    B. Stroustrup, The C++ Report, Vol. 12, No. 6, June 2000) that
    works in conjunction with the old_state variable in the
    abstract_vertex class.
 */
vv::abstract_vertex* vv::vptr::operator->() const {
  if (!ptr)
    vverr << "Membership indirection on a null vertex." << std::endl;
  ptr->release_old();
  return ptr;
}

/** @brief Indirection operation */
vv::abstract_vertex& vv::vptr::operator*() const {
  if (!ptr)
    vverr << "Indirection on a null vertex." << std::endl;
  return *ptr;
}

/** @brief Retrieve the raw abstract_vertex pointer.

    This function should usually only be used internally to the
    library.
*/
vv::abstract_vertex* vv::vptr::raw() const {
  return ptr;
}

/** @brief Cast to boolean to check if the pointer is null. */
vv::vptr::operator bool() const {
  return ptr;
}

void vv::vptr::release() {
  if (ptr) {
    ptr->refm();
    ptr = 0;
  }
}

/** @brief Destructor */
vv::abstract_vertex_iterator::~abstract_vertex_iterator() {}

/** @brief Constructor
    @param v The vertex to iterate on
    @param old Whether to iterate on the current state or the old state
 */
vv::neighbourhood_iterator::neighbourhood_iterator(vv::vptr v, bool old) :
  v(v),
  old(old),
  returned_first(false)
{
  if (v->valence() == 0) {
    vverr << "Attempt to iterate on a vertex with no neighbours" << std::endl;
    return;
  }
  start = old ? v->old()->any() : v->any();
  current = start;
}

/** @brief Return the starting vertex in the neighbourhood */
vv::vptr vv::neighbourhood_iterator::first() {
  return start;
}

/** @brief Get the next vertex in counterclockwise order */
vv::vptr vv::neighbourhood_iterator::advance() {
  returned_first = true;
  current = old ? v->old()->next(current) : v->next(current);
  return current;
}

/** @brief Check to see if there are more items to iterate over */
bool vv::neighbourhood_iterator::not_finished() {
  return !(returned_first && current == start);
}

/** @brief Constructor */
vv::abstract_vertex::abstract_vertex() :
  refs(0),
  old_state(0),
  nb(&nb_current)
{
  flag_current = nb_current.end();
  flag_old     = nb_old.end();
  fl           = &flag_current;
}

/** @brief Destructor */
vv::abstract_vertex::~abstract_vertex() {
  for (niter i = nb_current.begin(); i != nb_current.end(); ++i) {
    delete i->second;
  }
  nb_current.clear();
  for (niter i = nb_old.begin(); i != nb_old.end(); ++i) {
    delete i->second;
  }
  nb_old.clear();
}

/** @brief Increase the reference count.

    Usually, this function should only be called by a vptr.
*/
void vv::abstract_vertex::refp() {
  ++refs;
}

/** @brief Decrease the reference count.

    Usually, this function should only be called by a vptr.
*/
void vv::abstract_vertex::refm() {
  if (refs == 0)
    vverr << "Attempt to release an abstract_vertex without references" << std::endl;
  else
    --refs;
}

/** @brief Return the number of references.

    Usually, this function should only be called by the abstract_vertex
    factory.
*/
unsigned int vv::abstract_vertex::references() const {
  return refs;
}

/** @brief Set the abstract_vertex to use the old state.

    This function returns a vptr to the abstract_vertex, so it can be used
    in a chained expression (e.g. v->old()->any()).
*/
vv::vptr vv::abstract_vertex::old() {
  old_state = 2;
  nb = &nb_old;
  props = props_old;
  fl = &flag_old;
  return vptr(this);
}

/** @brief Returns the vertex to the current state after it is called
           twice without a call to old() in between.

    This function should usually only be called through a vptr.
*/
void vv::abstract_vertex::release_old() {
  if (old_state > 0) --old_state;
  if (old_state == 0) reset_state();
}

/** @brief Set the flag
    @param v The vertex to flag in the neighbourhood

    If v is null or is not in the neighbourhood, a warning is
    issued and there is no effect on the vertex.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::flag(vv::vptr v) {
  if (nb == &nb_old) {
    vverr << "flag() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (!v) {
    vvwarn << "flag() - The target vertex was null.  The flag was not changed." << std::endl;
    return;
  }

  nb_find_predicate pred(v.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);
  if (found == nb_current.end()) {
    vvwarn << "flag() - The target vertex was not found in the neighbourhood.  The flag was not changed." << std::endl;
    return;
  }
  flag_current = found;
}

/** @brief Return the flag

    If the flag is unset, a null vertex is returned.
*/
vv::vptr vv::abstract_vertex::flag() {
  if (*fl == nb->end()) return vv::vptr();
  else return (*fl)->first.raw();
}

/** @brief Construct and return a neighbourhood iterator */
vv::neighbourhood_iterator* vv::abstract_vertex::iter() {
  return new neighbourhood_iterator(this, nb == &nb_old);
}

/** @brief Synchronise the abstract_vertex data.

    When synchronise is called, the neighbours, properties and edge data
    are copied.
*/
void vv::abstract_vertex::synchronise() {
  synch_edges();
  for (niter i = nb_old.begin(); i != nb_old.end(); ++i) {
    delete i->second;
  }
  nb_old = nb_current;
  for (niter i = nb_old.begin(), j = nb_current.begin();
       i != nb_old.end(); ++i,++j) {
    i->second = new_edge();
    copy_edge(j->second, i->second);
    if (j == flag_current) flag_old = j;
  }
  copy_properties(props_current, props_old);
}

/** @brief Retrieve the number of neighbours. */
unsigned int vv::abstract_vertex::valence() {
  return nb->size();
}

/** @brief Check if a vertex is in the neighbourhood. */
bool vv::abstract_vertex::in(vv::vptr v) {
  nb_find_predicate pred(v.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);
  return found != nb->end();
}

/** @brief Retrieve some abstract_vertex from the neighbourhood.

    If the neighbourhood is empty, then a null abstract_vertex is
    returned.

    This function provides no guarantees as to what abstract_vertex in
    the neighbourhood is selected.
*/
vv::vptr vv::abstract_vertex::any() {
  if (!nb->size()) {
    vvwarn << "any() -  The neighbourhood was empty; a null vertex was returned." << std::endl;
    return vptr();
  }
  else return nb->front().first.raw();
}

/** @brief Retrieve the vertex that is `index' positions following v.
    @param v The vertex to find.
    @param index The number of positions to go passed v

    If v does not exist in the neighbourhood, then a warning is issued
    and null vertex is returned.
*/
vv::vptr vv::abstract_vertex::next(vv::vptr v, unsigned int index) {
  if (!v) {
    vvwarn << "next() - The target vertex is null; a null vertex was returned" << std::endl;
    return vptr();
  }

  nb_find_predicate pred(v.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);

  if (found == nb->end()) {
    vvwarn << "next() - The target vertex was not found; a null vertex was returned." << std::endl;
    return vptr();
  }

  for (unsigned int i = 0; i < index; ++i) {
    ++found;
    if (found == nb->end()) found = nb->begin();
  }

  return found->first.raw();
}

/** @brief Retrieve the vertex that is `index' positions preceding v.
    @param v The vertex to find.
    @param index The number of positions to go passed v

    If v does not exist in the neighbourhood, then a null vertex is
    returned.
*/
vv::vptr vv::abstract_vertex::prev(vv::vptr v, unsigned int index) {
  if (!v) {
    vvwarn << "prev() - The target vertex is null; a null vertex was returned" << std::endl;
    return vptr();
  }

  nb_find_predicate pred(v.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);

  if (found == nb->end()) {
    vvwarn << "prev() - The target vertex was not found; a null vertex was returned." << std::endl;
    return vptr();
  }

  for (unsigned int i = 0; i < index; ++i) {
    if (found == nb->begin()) found = nb->end();
    --found;
  }

  return found->first.raw();
}

/** @brief Retrieve the vertex that is `index' positions after the flag
    @brief index The number of positions to go passed the flag

    If the flag is not set, a warning is issued and a null vertex is
    returned,
*/
vv::vptr vv::abstract_vertex::next_flag(unsigned int index) {
  if (*fl == nb->end()) {
    vvwarn << "next_flag() - The flag was not set; a null vertex was returned." << std::endl;
    return vptr();
  }

  niter found = *fl;
  for (unsigned int i = 0; i < index; ++i) {
    ++found;
    if (found == nb->end()) found = nb->begin();
  }

  return found->first.raw();
}

/** @brief Retrieve the vertex that is `index' positions before the flag
    @brief index The number of positions to go passed the flag

    If the flag is not set, a warning is issued and a null vertex is
    returned,
*/
vv::vptr vv::abstract_vertex::prev_flag(unsigned int index) {
  if (*fl == nb->end()) {
    vvwarn << "prev_flag() - The flag was not set; a null vertex was returned." << std::endl;
    return vptr();
  }

  niter found = *fl;
  for (unsigned int i = 0; i < index; ++i) {
    if (found == nb->begin()) found = nb->end();
    --found;
  }

  return found->first.raw();
}

/** @brief Neighbourhood Asssignment.
    @param n The neighbourhood to assign

    Assignment of a neighbourhood invalidates the flag.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::assign_nb(const vv::nb& n) {
  if (nb == &nb_old) {
    vverr << "assign_nb() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }
  for (niter i = nb_current.begin(); i != nb_current.end(); ++i) {
    delete i->second;
  }
  nb_current.clear();
  for (vv::nb::const_iterator i = n.begin(); i != n.end(); ++i) {
    nb_current.push_back(nb_pair(i->raw(), new_edge()));
  }
  flag_current = nb_current.end();
}

/** @brief Remove a vertex from the neighbourhood.
    @param v A vptr to the vertex to be removed.

    If v is null or does not refer to a vertex in the neighbourhood,
    then a warning is issued.

    If the flagged vertex is removed, the flag is invalidated.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::remove(vv::vptr v) {
  if (nb == &nb_old) {
    vverr << "remove() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (!v) {
    vvwarn << "remove() - The target vertex is null." << std::endl;
    return;
  }

  nb_find_predicate pred(v.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);

  if (found == nb->end()) {
    vvwarn << "remove() - The target vertex was not found." << std::endl;
    return;
  }

  if (found == flag_current) {
    flag_current = nb_current.end();
  }

  delete found->second;
  nb->erase(found);
}

/** @brief Remove the flagged vertex from the neighbourhood.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::remove_flag() {
  if (nb == &nb_old) {
    vverr << "remove_flag() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (flag_current == nb->end()) {
    vvwarn << "remove_flag() - The target vertex was not found." << std::endl;
    return;
  }

  delete flag_current->second;
  nb->erase(flag_current);
  flag_current = nb_current.end();
}


/** @brief Replace a vertex in the neighbourhood.
    @param v The vertex to add to the neighbourhood.
    @param t The target vertex to be replaced.

    If eiher v or t is null or t does not exist in the neighbourhood,
    then this function has no effect.  If v already exists in the
    neighbourhood, a warning is issued and the neighbourhod is not
    affected.

    The edge to the vertex begin replaced is removed in this operation
    and a new edge is made to the new vertex.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::replace(vv::vptr t, vv::vptr v) {
  if (nb == &nb_old) {
    vverr << "replace() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (!v || !t) {
    vvwarn << "replace() - One of the vertices is null." << std::endl;
    return;
  }

  nb_find_predicate pred(t.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);

  if (found == nb->end()) {
    vvwarn << "replace() - The target vertex was not found." << std::endl;
    return;
  }

  found->first = v.raw();
  delete found->second;
  found->second = new_edge();
}

/** @brief Replace vertex in the flagged position in the neighbourhood.
    @param v The vertex to add to the neighbourhood.

    If eiher v is null or the flag is not set, then this function has
    no effect.  If v already exists in the neighbourhood, a warning is
    issued and the neighbourhod is not affected.

    The edge to the vertex begin replaced is removed in this operation
    and a new edge is made to the new vertex.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::replace_flag(vv::vptr v) {
  if (nb == &nb_old) {
    vverr << "replace_flag() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (!v) {
    vvwarn << "replace_flag() - The vertex is null." << std::endl;
    return;
  }

  if (flag_current == nb->end()) {
    vvwarn << "replace_flag() - The flag was not set." << std::endl;
    return;
  }

  flag_current->first = v.raw();
  delete flag_current->second;
  flag_current->second = new_edge();
}

/** @brief Add a vertex into the neighbourhood before the indicated vertex.
    @param t The vertex that marks the position in the neighbourhood.
    @param v The vertex to add to the neighbourhood.

    If t or v is null, or if t is not in the neihgbourhood, this
    function will issue a warning and have no effect.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::splice_before(vv::vptr t, vv::vptr v) {
  if (nb == &nb_old) {
    vverr << "splice_before() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (!v || !t) {
    vvwarn << "splice_before() - One of the vertices is null." << std::endl;
    return;
  }

  nb_find_predicate pred(t.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);

  if (found == nb->end()) {
    vvwarn << "splice_before() - The target vertex was not found." << std::endl;
    return;
  }

  nb_pair p = std::make_pair(v.raw(), new_edge());
  nb->insert(found, p);
}

/** @brief Add a vertex into the neighbourhood after the indicated vertex.
    @param t The vertex that marks the position in the neighbourhood.
    @param v The vertex to add to the neighbourhood.

    If t or v is null, or if t is not in the neihgbourhood, this
    function will issue a warning and have no effect.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::splice_after(vv::vptr t, vv::vptr v) {
  if (nb == &nb_old) {
    vverr << "splice_after() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (!v || !t) {
    vvwarn << "splice_after() - One of the vertices is null." << std::endl;
    return;
  }

  nb_find_predicate pred(t.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);

  if (found == nb->end()) {
    vvwarn << "splice_after() - The target vertex was not found." << std::endl;
    return;
  }

  ++found;
  nb_pair p = std::make_pair(v.raw(), new_edge());
  nb->insert(found, p);
}

/** @brief Add a vertex into the neighbourhood before the flag.
    @param v The vertex to add to the neighbourhood.

    If v is null, or if the flag is not set, this function will issue
    a warning and have no effect.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::splice_before_flag(vv::vptr v) {
  if (nb == &nb_old) {
    vverr << "splice_before_flag() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (!v) {
    vvwarn << "splice_before_flag() - The vertices is null." << std::endl;
    return;
  }

  if (flag_current == nb->end()) {
    vvwarn << "splice_before_flag() - The target vertex was not found." << std::endl;
    return;
  }

  nb_pair p = std::make_pair(v.raw(), new_edge());
  nb->insert(flag_current, p);
}

/** @brief Add a vertex into the neighbourhood before the flag.
    @param v The vertex to add to the neighbourhood.

    If v is null, or if the flag is not set, this function will issue
    a warning and have no effect.

    If the vertex is in the old state, an error is issued and there is
    no effect on the vertex.
*/
void vv::abstract_vertex::splice_after_flag(vv::vptr v) {
  if (nb == &nb_old) {
    vverr << "splice_after_flag() - The vertex is in the old state and not modifiable." << std::endl;
    return;
  }

  if (!v) {
    vvwarn << "splice_after_flag() - The vertices is null." << std::endl;
    return;
  }

  if (flag_current == nb->end()) {
    vvwarn << "splice_after_flag() - The target vertex was not found." << std::endl;
    return;
  }

  niter i = flag_current;
  ++i;
  nb_pair p = std::make_pair(v.raw(), new_edge());
  nb->insert(i, p);
}

/** @brief Retrieve the properties structure. */
vv::abstract_vertex::abstract_properties* vv::abstract_vertex::properties() {
  return props;
}

/** @brief Retrieve an edge.
    @param v The vertex that the edge is to.

    If v is null or not in the neighbourhood, a null pointer is
    returned and a warning is issued.
*/
vv::abstract_vertex::abstract_edge* vv::abstract_vertex::asymmetric_edge(vv::vptr v) {
  if (!v) {
    vvwarn << "asymmetric_edge() - The target vertex is null." << std::endl;
    return 0;
  }

  nb_find_predicate pred(v.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);

  if (found == nb->end()) {
    vvwarn << "asymmetric_edge() - The target vertex was not found." << std::endl;
    return 0;
  }

  if (nb == &nb_current) synch_edges();

  return found->second;
}

/** @brief Retrieve an edge.
    @param v The vertex that the edge is to.

    When the half-edge to v is accessed with this function, the
    information is afterwards copied to the half-edge from v back to
    the current vertex.

    If v is null or not in the neighbourhood, a null pointer is
    returned and a warning is issued.
*/
vv::abstract_vertex::abstract_edge* vv::abstract_vertex::symmetric_edge(vv::vptr v) {
  if (!v) {
    vvwarn << "symmetric_edge() - The target vertex is null." << std::endl;
    return 0;
  }

  nb_find_predicate pred(v.raw());
  niter found = std::find_if(nb->begin(), nb->end(), pred);

  if (found == nb->end()) {
    vvwarn << "symmetric_edge() - The target vertex was not found." << std::endl;
    return 0;
  }

  if (nb == &nb_current) {
    synch_edges();
    v->synch_edge(this);
  }

  return found->second;
}

void vv::abstract_vertex::init_props() {
  props_current = new_properties();
  props_old = new_properties();
  props = props_current;
}

vv::abstract_vertex::nb_find_predicate::nb_find_predicate(vv::abstract_vertex* v) :
  v(v)
{}

bool vv::abstract_vertex::nb_find_predicate::operator()(const vv::abstract_vertex::nb_pair& np) {
  return v == np.first.raw();
}

void vv::abstract_vertex::reset_state() {
  old_state = 0;
  nb = &nb_current;
  props = props_current;
  fl = &flag_current;
}

void vv::abstract_vertex::synch_edge(nptr v) {
  unsynched_edges.push(v);
}

void vv::abstract_vertex::synch_edges() {
  while (!unsynched_edges.empty()) {
    abstract_vertex* to = unsynched_edges.front().raw();

    nb_find_predicate pred_to(to);
    niter found_to = std::find_if(nb_current.begin(), nb_current.end(), pred_to);

    nb_find_predicate pred_from(this);
    niter found_from = std::find_if(to->nb_current.begin(), to->nb_current.end(), pred_from);

    if (found_to != nb_current.end() && found_from != to->nb_current.end()) {
      copy_edge(found_from->second, found_to->second);
    }

    unsynched_edges.pop();
  }
}

bool operator< (const vv::vptr& a, const vv::vptr& b) {
  return a.raw() < b.raw();
}

bool operator> (const vv::vptr& a, const vv::vptr& b) {
  return a.raw() > b.raw();
}

bool operator==(const vv::vptr& a, const vv::vptr& b) {
  return a.raw() == b.raw();
}

bool operator!=(const vv::vptr& a, const vv::vptr& b) {
  return a.raw() != b.raw();
}
